package com.lemon.exam.common.crud;

import com.lemon.exam.common.annotation.LogicDelete;
import com.lemon.exam.common.enums.DeleteEnum;
import com.lemon.exam.common.enums.StatusEnum;
import com.lemon.exam.common.exception.CustomException;
import com.lemon.exam.common.util.CriteriaUtil;
import com.lemon.exam.common.web.Param;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.core.query.Update;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;

import static com.lemon.exam.common.constant.ApiConst.*;
import static com.lemon.exam.common.enums.CodeEnum.ERROR_402;

/**
 * 基本Service实现类，主要配置基本的增删改查，用来被继承
 *
 * @author Lemon
 * @since 2025/3/24 11:47
 */
@Log4j2
public abstract class BaseServiceImpl<T, ID, R extends R2dbcRepository<T, ID>> implements BaseService<T, ID> {
    //实体类类对象
    @SuppressWarnings("unchecked")
    protected final Class<T> entityClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

    //实体类类对象
    protected final String tableName;

    //主键字段
    protected final String primaryKey = Arrays.stream(this.entityClass.getDeclaredFields())
            .filter(field -> field.isAnnotationPresent(Id.class)).map(Field::getName).findFirst().orElse(null);

    //逻辑删除字段
    protected final Field logicDelete = Arrays.stream(this.entityClass.getDeclaredFields())
            .filter(field -> field.isAnnotationPresent(LogicDelete.class)).findFirst().orElse(null);

    //属性集合
    protected final List<Field> fieldList = Arrays.stream(this.entityClass.getDeclaredFields())
            .filter(field -> !field.isAnnotationPresent(Transient.class))
            .filter(field -> !Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers()))
            .toList();

    @Autowired(required = false)
    protected R repository;

    @Autowired(required = false)
    protected R2dbcEntityTemplate template;

    @Autowired(required = false)
    protected DatabaseClient databaseClient;

    @Autowired(required = false)
    protected TransactionalOperator transactionalOperator;


    public BaseServiceImpl() {
        //表名
        Table table = this.entityClass.getAnnotation(Table.class);
        if (null == table) {
            this.tableName = DefaultNamingStrategy.INSTANCE.getTableName(this.entityClass);
        } else {
            if (StringUtils.hasText(table.value())) {
                this.tableName = StringUtils.hasText(table.schema()) ? table.schema() + "." + table.value() : table.value();
            } else if (StringUtils.hasText(table.name())) {
                this.tableName = StringUtils.hasText(table.schema()) ? table.schema() + "." + table.name() : table.name();
            } else {
                throw new CustomException("实体类%s未设置表名！".formatted(this.entityClass.getName()));
            }
        }
    }

    @Override
    public Mono<String> insert(T param) {
        return template.insert(param).thenReturn(INSERT_SUCCESS)
                .onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
    }

    /**
     * 只修改非空字段
     *
     * @param param
     * @return
     */
    @Override
    public Mono<String> update(T param) {
        return Mono.zip(
                this.getPrimaryKeyValue(param),
                this.convertUpdateParam(param)
        ).flatMap(tuple -> template.update(entityClass)
                .matching(Query.query(Criteria.where(this.primaryKey).is(tuple.getT1())))
                .apply(tuple.getT2())
                .thenReturn(UPDATE_SUCCESS)
                .onErrorResume(err -> Mono.error(new CustomException(ERROR_402))));

    }

    @Override
    public Mono<String> deleteById(ID id) {
        return Mono.defer(() -> null == this.logicDelete ? repository.deleteById(id) : this.logicDelete(id))
                .thenReturn(DELETE_SUCCESS)
                .onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
    }


    @Override
    public Mono<?> findById(ID id) {
        return repository.findById(id);
    }

    @Override
    public Mono<Boolean> existsById(ID id) {
        return Mono.defer(() -> null == this.logicDelete ? repository.existsById(id) : this.existsByLogicDelete(id));
    }


    @Override
    public Flux<?> findAllById(Iterable<ID> ids) {
        return repository.findAllById(ids);
    }

    @Override
    public Mono<Long> count(Map<String, Object> param) {
        return Mono.zip(this.logicDeleteCriteria(), CriteriaUtil.toCriteria(param))
                .map(tuple -> tuple.getT1().and(tuple.getT2()))
                .map(Query::query)
                .flatMap(query -> template.count(query, this.entityClass));
    }

    @Override
    public Flux<?> find(Map<String, Object> param) {
        return Mono.zip(this.logicDeleteCriteria(), CriteriaUtil.toCriteria(param))
                .map(tuple -> tuple.getT1().and(tuple.getT2()))
                .map(Query::query)
                .flatMapMany(query -> template.select(query, this.entityClass));

    }

    @Override
    public Mono<Page<?>> page(Param param) {
        return Mono.zip(param.getCriteria(), this.logicDeleteCriteria())
                .map(tuple -> tuple.getT1().and(tuple.getT2()))
                .zipWith(param.getPageRequest())
                .flatMap(tuple -> Mono.zip(
                        template.select(Query.query(tuple.getT1()).with(tuple.getT2()), this.entityClass).collectList().subscribeOn(Schedulers.parallel()),
                        template.count(Query.query(tuple.getT1()), this.entityClass).subscribeOn(Schedulers.parallel())
                ).map(tu -> new PageImpl<>(new ArrayList<>(tu.getT1()), tuple.getT2(), tu.getT2())));
    }

    @Override
    public Mono<String> enable(ID id) {
        return template.update(entityClass)
                .matching(Query.query(Criteria.where(this.primaryKey).is(id)))
                .apply(Update.update(StatusEnum.ENABLE.getColumn(), StatusEnum.ENABLE.getValue()))
                .thenReturn(ENABLE_SUCCESS)
                .onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
    }

    @Override
    public Mono<String> unable(ID id) {
        return template.update(entityClass)
                .matching(Query.query(Criteria.where(this.primaryKey).is(id)))
                .apply(Update.update(StatusEnum.UNABLE.getColumn(), StatusEnum.UNABLE.getValue()))
                .thenReturn(UNABLE_SUCCESS)
                .onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
    }

    /**
     * 获取主键值
     *
     * @param param
     * @return
     */
    protected Mono<Object> getPrimaryKeyValue(T param) {
        return Mono.fromCallable(() -> {
            Field primaryKeyField = this.fieldList
                    .parallelStream()
                    .filter(field -> field.getName().equals(this.primaryKey))
                    .findFirst()
                    .orElse(null);
            if (null != primaryKeyField) {
                primaryKeyField.setAccessible(true);
                return primaryKeyField.get(param);
            }
            return null;
        }).subscribeOn(Schedulers.parallel());
    }

    /**
     * 将T转换为Update对象
     *
     * @param param
     * @return
     */
    protected Mono<Update> convertUpdateParam(T param) {
        return Mono.fromCallable(() -> {
            Map<SqlIdentifier, Object> map = new HashMap<>();
            for (Field field : fieldList) {
                String name = field.getName();
                if (name.equals(this.primaryKey)) {
                    continue;
                }
                try {
                    field.setAccessible(true);
                    Object value = field.get(param);
                    if (null != value) {
                        map.put(SqlIdentifier.unquoted(name), value);
                    }
                } catch (IllegalAccessException e) {
                    log.error("字段{}获取值异常", name, e);
                }
            }
            return Update.from(map);
        }).subscribeOn(Schedulers.parallel());
    }

    /**
     * 逻辑删除
     *
     * @param id
     * @return
     */
    protected Mono<Boolean> logicDelete(ID id) {
        return template.update(entityClass)
                .matching(Query.query(Criteria.where(this.primaryKey).is(id)))
                .apply(Update.update(this.logicDelete.getName(), DeleteEnum.ENABLE.getValue()))
                .flatMap(rowsUpdated -> rowsUpdated == 1L ? Mono.just(true) : Mono.error(new CustomException(ERROR_402)));
    }

    /**
     * 逻辑删除字段判断是否存在
     *
     * @param id
     * @return
     */
    protected Mono<Boolean> existsByLogicDelete(ID id) {
        return template.select(this.entityClass)
                .matching(Query.query(Criteria.where(this.primaryKey).is(id).and(Criteria.where(this.logicDelete.getName()).not(DeleteEnum.ENABLE.getValue()))))
                .exists();
    }

    /**
     * 添加逻辑删除字段的条件
     *
     * @return
     */
    protected Mono<Criteria> logicDeleteCriteria() {
        return Mono.fromSupplier(() -> {
            if (null == this.logicDelete) {
                return Criteria.empty();
            }
            return Criteria.where(this.logicDelete.getName()).not(DeleteEnum.ENABLE.getValue());
        }).subscribeOn(Schedulers.parallel());
    }

    /**
     * 判断字段是否存在
     *
     * @param filed
     * @param value
     * @return
     */
    protected Mono<Boolean> exist(String filed, Object value) {
        return template.select(this.entityClass)
                .matching(Query.query(Criteria.where(filed).is(value)))
                .exists().subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 判断字段是否存在，不包含主键
     *
     * @param filed
     * @param value
     * @return
     */
    protected Mono<Boolean> exist(String filed, Object value, Object primaryKey) {
        return template.select(this.entityClass)
                .matching(Query.query(Criteria.where(filed).is(value).and(Criteria.where(this.primaryKey).not(primaryKey))))
                .exists()
                .subscribeOn(Schedulers.boundedElastic());
    }
}
